/*
    Android Asynchronous Http Client
    Copyright (c) 2013 JIXIAOLONG <MicroJixl@gmail.com>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */
package com.jxl.netframe;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.util.SparseArray;

import com.jxl.netframe.apachebase.ApacheHttpClient;
import com.jxl.netframe.log.Logger;

/** 
 * @author JiXL
 * @time 2013-6-6 下午1:47:33
 * <br>
 * For example:
 * <br>
 *  <code>NetCenter center = NetCenter.getInstance();</code>
 *  <br><code>center.startService();</code>
 *  <br><code>ProcessListener callback = new AsyncHttpResponseHandler<String>() {</code>
 *		
 *  <br><code>	public void onUpBufferd(RequestParameter requestParameter, long up, long total) {</code>
 *  <br><code>		// TODO Auto-generated method stub</code>
 *			
 *  <br><code>		}</code>
 *		
 *  <br><code>	public void onRetry(RequestParameter requestParameter, int count, Exception e) {</code>
 *  <br><code>			// TODO Auto-generated method stub</code>
 * 			
 *  <br><code>	}</code>
 *		
 *	<br><code>	public void onFinished(RequestParameter requestParameter, String response) {</code>
 *	<br><code>		Toast.makeText(UseageTest.this, "finish"+requestParameter.getUrl(), Toast.LENGTH_SHORT).show();</code>
 *			
 *	<br><code>	}</code>
 *		
 *	<br><code>	public void onFailed(RequestParameter requestParameter, Exception e) {</code>
 *	<br><code>		// TODO Auto-generated method stub</code>
 *			
 *	<br><code>	}</code>
 *		
 *	<br><code>	public void onDownBufferd(RequestParameter requestParameter, long down, long total) {</code>
 *	<br><code>		// TODO Auto-generated method stub</code>
 *			
 *	<br><code>	}</code>
 *		
 *	<br><code>	public void onCanceled(RequestParameter requestParameter) {</code>
 *	<br><code>		// TODO Auto-generated method stub</code>
 *			
 *	<br><code>	}</code>
 *		
 *	<br><code>	public String parser(String response) {</code>
 *	<br><code>		return response;</code>
 *	<br><code>	}</code>
 *  <br><code>};</code>
 *	<br><code>RequestParameter rq = RequestParameter.build("www.baidu.com");</code>
 *	<br><code>RequestParameter rq1 = RequestParameter.build("http://www.baidu.com");</code>
 *	<br><code>RequestParameter rq3 = RequestParameter.build("http://www.sina.com");</code>
 *	<br><code>RequestParameter rq2 = RequestParameter.build("http://www.google.com");</code>
 *	<br><code>center.get(rq,callback);</code>
 *	<br><code>center.get(rq1,callback);</code>
 *	<br><code>center.get(rq3,callback);</code>
 *	<br><code>center.get(rq2,callback);</code>
 *	<br><code>center.cancel(rq2.allocateID());</code>
 *  <br>
 * {@link #NetCenter Note:}
 * <br>
 * you need invoke {@link #shutDown()} when you exit system or never use center}
 */
public class NetCenter{
	private final String TAG = getClass().getName();
	private LinkedList<RequestParameter> smallRequestQueue = null;
	private LinkedList<RequestParameter> bigRequestQueue = null;
	private SparseArray<List<ProcessListener>> dispatcherMap = null;
	private Lock lock = null;
	private ApacheHttpClient httpClient;
	private QueueTask smallQueueTask;
	private QueueTask bigQueueTask;
	private ProcessListener centerProcessListener = null;
	private boolean isRuning = false;
	private static NetCenter center = null;
	public static NetCenter getInstance(){
		if(center == null){
			center = new NetCenter();
		}
		return center;
	}

	public void startService(){
		if(isRuning){
			Logger.d(TAG, "Net Center service is already started!");
			return;
		}
		Logger.d(TAG, "Net Center service start...");
		init();
		smallQueueTask.start(); 
		bigQueueTask.start(); 
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		isRuning = true;
	}
	
	public void setLog(int level){
		Logger.TAG_Filter = level;
	}

	public void configHead(String header,String value){
		if(!isRuning)return;
		httpClient.addHeader(header, value);
	}

	public void configHead(Map<String, String> head){
		for (Iterator<Entry<String, String>> iterator = head.entrySet().iterator();iterator.hasNext();) {
			Entry<String, String> entry = iterator.next();
			configHead(entry.getKey(), entry.getValue());
		}
	}

	private void init(){
		smallRequestQueue = new LinkedList<RequestParameter>();
		bigRequestQueue = new LinkedList<RequestParameter>();
		dispatcherMap = new SparseArray<List<ProcessListener>>();
		lock = new ReentrantLock();
		httpClient = new ApacheHttpClient();
		centerProcessListener = new ProcessListener() {

			/**
			 * ==========================================================================
			 * the notification about request.
			 * ==========================================================================
			 */
			@Override
			public void upBufferd(RequestParameter requestParameter, long up, long total) {
				List<ProcessListener> listeners = dispatcherMap.get(requestParameter.getId());
				if(listeners != null){
					for(Iterator<ProcessListener> iterator = listeners.iterator();iterator.hasNext();){
						iterator.next().upBufferd(requestParameter, up, total);
					}
				}
				Logger.i(TAG, "[url:"+requestParameter.getUrl()+"]upBufferd:total:"+total+" up↑:"+up);
			}
			@Override
			public void downBufferd(RequestParameter requestParameter, long down,
					long total) {
				List<ProcessListener> listeners = dispatcherMap.get(requestParameter.getId());
				if(listeners != null){
					for(Iterator<ProcessListener> iterator = listeners.iterator();iterator.hasNext();){
						iterator.next().downBufferd(requestParameter, down, total);
					}
				}
				Logger.i(TAG, "[url:"+requestParameter.getUrl()+"]downBufferd:total:"+total+" down↓:"+down);
			}
			@Override
			public void finished(RequestParameter requestParameter, String response) {
				lock.lock();
				try {
					List<ProcessListener> listeners = dispatcherMap.get(requestParameter.getId());
					if(listeners != null){
						for(Iterator<ProcessListener> iterator = listeners.iterator();iterator.hasNext();){
							iterator.next().finished(requestParameter, response);
						}
						dispatcherMap.remove(requestParameter.getId());
					}
				} finally{
					lock.unlock();
				}
				Logger.i(TAG, "finished[url:"+requestParameter.getUrl()+"]");
			}
			@Override
			public void canceled(RequestParameter requestParameter) {
				lock.lock();
				try {
					List<ProcessListener> listeners = dispatcherMap.get(requestParameter.getId());
					if(listeners != null){
						for(Iterator<ProcessListener> iterator = listeners.iterator();iterator.hasNext();){
							iterator.next().canceled(requestParameter);
						}
						dispatcherMap.remove(requestParameter.getId());
					}
				} finally{
					lock.unlock();
				}
				Logger.i(TAG, "canceled[url:"+requestParameter.getUrl()+"]");
			}
			@Override
			public void failed(RequestParameter requestParameter, Exception e) {
				lock.lock();
				try {
					List<ProcessListener> listeners = dispatcherMap
							.get(requestParameter.getId());
					if (listeners != null) {
						for (Iterator<ProcessListener> iterator = listeners.iterator(); iterator
								.hasNext();) {
							iterator.next().failed(requestParameter, e);
						}
						dispatcherMap.remove(requestParameter.getId());
					}
				} finally{
					lock.unlock();
				}
				Logger.i(TAG, "failed[url:"+requestParameter.getUrl()+"] failed exception:"+e.getMessage());
			}
			@Override
			public void retry(RequestParameter requestParameter, int count, Exception e) {
				List<ProcessListener> listeners = dispatcherMap.get(requestParameter.getId());
				if(listeners != null){
					for(Iterator<ProcessListener> iterator = listeners.iterator();iterator.hasNext();){
						iterator.next().retry(requestParameter, count, e);
					}
				}
				Logger.i(TAG, "request retry[url:"+requestParameter.getUrl()+"] count:"+count+";retry exception:"+e.getMessage());
			}
		};
		bigQueueTask = new QueueTask(httpClient, bigRequestQueue, centerProcessListener);
		smallQueueTask = new QueueTask(httpClient, smallRequestQueue, centerProcessListener);
	}

	public void shutDown(){
		if(!isRuning)return;
		cancelAll();
		smallQueueTask.shutDown();
		bigQueueTask.shutDown();
		httpClient.shutDown();
		lock.lock();
		try{
			dispatcherMap.clear();
		}finally{
			lock.unlock();
		}
		isRuning = false;
	}

	public void post(RequestParameter rq, ProcessListener callback){
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
			callback.failed(rq, new IllegalStateException("service not start."));
			//			throw new IllegalStateException("service not start.");
			return;
		}
		if(rq == null){
			return;
		}
		rq.setMethod(RequestParameter.POST);
		processRequest(rq, callback);
	}

	public void get(RequestParameter rq,ProcessListener callback) throws IllegalStateException{
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
//			throw new IllegalStateException("service not start.");
			callback.failed(rq, new IllegalStateException("service not start."));
			return;
		}
		if(rq == null){
			return;
		}
		rq.setMethod(RequestParameter.GET);
		processRequest(rq, callback);
	}

	public void put(RequestParameter rq,ProcessListener callback) throws IllegalStateException{
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
//			throw new IllegalStateException("service not start.");
			callback.failed(rq, new IllegalStateException("service not start."));
			return;
		}
		if(rq == null){
			return;
		}
		rq.setMethod(RequestParameter.PUT);
		processRequest(rq, callback);
	}

	public void delete(RequestParameter rq,ProcessListener callback) throws IllegalStateException{
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
//			throw new IllegalStateException("service not start.");
			callback.failed(rq, new IllegalStateException("service not start."));
			return;
		}
		if(rq == null){
			return;
		}
		rq.setMethod(RequestParameter.DELETE);
		processRequest(rq, callback);
	}

	private void processRequest(RequestParameter rq, ProcessListener callback){
		rq.allocateID();
		LinkedList<RequestParameter> queue = null;
		lock.lock();
		try{
			if(rq.getMethod() == RequestParameter.POST && rq.isFile()){
				if(rq.equals(bigQueueTask.getCurrentProcess())){
					return;
				}
				queue = bigRequestQueue;
			}else{
				queue = smallRequestQueue;
				if(rq.equals(bigQueueTask.getCurrentProcess())){
					return;
				}
			}

			//TODO 通过查找注册者确认是否是重复的包感觉有点问题。是否应该检查队列来确保重复的包？
			/**
			 * use dispatcherMap has high performance.
			 */
			List<ProcessListener> listeners = dispatcherMap.get(rq.getId());
			if(listeners == null){
				if(rq.getPriority() > RequestParameter.NORMAL){
					queue.addFirst(rq);
				}else{
					queue.add(rq);
				}
				listeners = new ArrayList<ProcessListener>();
			}else{
				//with high priority need move request to first.
				if(rq.getPriority() > RequestParameter.NORMAL){
					for(Iterator<RequestParameter> iterator = queue.iterator();iterator.hasNext();){
						if(iterator.next().equals(rq)){
							iterator.remove();
							queue.addFirst(rq);
						}
					}
				}
			}
			//only register the callback。
			if(callback != null){
				listeners.add(callback);
			}
			dispatcherMap.put(rq.getId(), listeners);
		}finally{
			lock.unlock();
		}

		if(rq.getMethod() == RequestParameter.POST && rq.isFile()){
			bigQueueTask.process();
		}else{
			smallQueueTask.process();
		}
	}

	public void cancelAll() throws IllegalStateException{
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
			throw new IllegalStateException("service not start.");
		}
		lock.lock();
		try{
			smallRequestQueue.clear();
			bigRequestQueue.clear();
			dispatcherMap.clear();
			smallQueueTask.cancel();
			bigQueueTask.cancel();
		}finally{
			lock.unlock();
		}

	}

	public void cancel(RequestParameter rq) throws IllegalStateException{
		if(!isRuning){
			Logger.d(TAG, "Net Center service can not provide services,may be you need start service first.");
			throw new IllegalStateException("service not start.");
		}
		if(rq == null)return;
		if(rq.getId() == -1 && rq.allocateID()==-1){
			return;
		}
		int id = rq.getId();
		lock.lock();
		try{
			List<ProcessListener> listeners = dispatcherMap.get(id);
			if(listeners != null){
				//this request is in queue.remove from queue.
				if(!removeSmallQueue(id) && !removeBigQueue(id)){
					//means this request is on process.
					if(smallQueueTask.cancel(id) && bigQueueTask.cancel(id)){};
				}else{
					//this request process completed or removed from queue.
					for(ProcessListener processListener:listeners){
						processListener.canceled(rq);
					}
				}
				//unregister callback.
				dispatcherMap.remove(id);
				Logger.d(TAG, "cancel request id:"+id);
			}
		}finally{
			lock.unlock();
		}
	}

	private boolean removeSmallQueue(int id){
		for(Iterator<RequestParameter> iterator = smallRequestQueue.iterator();iterator.hasNext();){
			if(iterator.next().getId() == id){
				iterator.remove();
				return true;
			}
		}
		return false;
	}
	private boolean removeBigQueue(int id){
		for(Iterator<RequestParameter> iterator = bigRequestQueue.iterator();iterator.hasNext();){
			if(iterator.next().getId() == id){
				iterator.remove();
				return true;
			}
		}
		return false;
	}

	public void cancel(int id) throws IllegalStateException{
		RequestParameter rq = RequestParameter.build("http://127.0.0.1");
		rq.setId(id);
		cancel(rq);
	}
}
